package com.boot2.core;

import com.alibaba.fastjson.JSON;
import com.boot2.core.aop.AbstractAop;
import com.boot2.core.constant.CoreConstant;
import com.boot2.core.dict.ErrorCode;
import com.boot2.core.exception.ErrorCodeException;
import com.boot2.core.exception.NotLoginedException;
import com.boot2.core.exception.ParamEmptyException;
import com.boot2.core.exception.TokenValidationException;
import com.boot2.core.feign.FeignBasicAuthRequestInterceptor;
import com.boot2.core.model.AbstractLog;
import com.boot2.core.model.vo.Result;
import com.boot2.core.service.AopService;
import com.boot2.core.service.SysExceptionService;
import com.boot2.core.utils.DateUtil;
import com.boot2.core.utils.ResultBuilder;
import com.boot2.core.utils.ServletUtil;
import com.boot2.core.utils.StringUtils;
import com.boot2.core.utils.TemplatesUtil;
import com.boot2.core.web.RequestContext;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestClientException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.thymeleaf.TemplateEngine;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map;

/**
 * @author zhangweilin
 * @date 2018/9/14 21:08
 * @description:全局异常处理
 */
@ControllerAdvice
@ResponseBody
@CommonsLog
public class GlobalExceptionHandler {

    @Autowired
    TemplateEngine templateEngine;

    @Autowired
    AopService aopService;

    @Autowired(required = false)
    private SysExceptionService sysExceptionService;

    /**
     * 参数为空异常处理
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(ErrorCodeException.class)
    @ResponseBody
    public Result<String> exceptionErrorCode(ErrorCodeException exception) throws Exception {
//        Response<String> result = resultError(exception.getErrorCode(), exception.getErrorMessage());
        sendExceptionInfo(exception);
//        return result;

        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = resultError(exception.getErrorCode(), exception.getErrorMessage());
            Result<String> result = ResultBuilder.buildResult(exception.getErrorCode(), exception.getErrorMessage());
            return result;
            // 返回错误页面
        } else {
            handException(exception, true);
            return null;
        }
    }

    /**
     * 参数为空异常处理
     * pageInfo
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(TokenValidationException.class)
    @ResponseBody
    public Result<String> exceptionTokenValidation(TokenValidationException exception) throws Exception {
//        Response<String> result = result401(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;
        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result400(exception.getMessage());
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_403, exception.getMessage());
            return result;
            // 返回错误页面
        } else {
            handException(exception, true);
            return null;
        }
    }

    /**
     * 未登陆异常
     *
     * @param exception
     * @return
     * @throws Exception
     */
    @ExceptionHandler(NotLoginedException.class)
    @ResponseBody
    public Result<String> exceptionNotLogined(NotLoginedException exception) throws Exception {
//        Response<String> result = result401(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;
        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result401(exception.getMessage());
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_401, "not logined");
            return result;
            // 返回错误页面
        } else {
            handException(exception, true);
            return null;
        }
    }

    /**
     * 参数为空异常处理
     * pageInfo
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(value = {
            ParamEmptyException.class,
            BindException.class,
            ServletRequestBindingException.class,
            UnsatisfiedServletRequestParameterException.class
    })
    @ResponseBody
    public Result<String> exceptionParam1(Exception exception) throws Exception {
//        Response<String> result = result400(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;
        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result400(exception.getMessage());
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_400, " param is invalid !!!");
            return result;
            // 返回错误页面
        } else {
            handException(exception, true);
            return null;
        }
    }

    /**
     * 参数为空异常处理
     * pageInfo
     *
     * @param exception
     * @return
     */
    @ExceptionHandler({MissingServletRequestParameterException.class,
            MissingPathVariableException.class,})
    @ResponseBody
    public Result<String> exceptionParam2(Exception exception) throws Exception {
//        Response<String> result = result400(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;
        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result500("param error");
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_400, exception.getMessage());
            return result;
            // 返回错误页面
        } else {
            handException(exception, false);
            return null;
        }
    }

    /**
     * 参数为空异常处理
     * pageInfo
     *
     * @param exception
     * @return
     */
    @ExceptionHandler({MaxUploadSizeExceededException.class, SizeLimitExceededException.class, FileUploadException.class})
    @ResponseBody
    public Result<String> exceptionMaxUploadSizeExceededException(Exception exception) throws Exception {
//        Response<String> result = result400(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;

        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result500("param error");
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_600, exception.getMessage());
            return result;
            // 返回错误页面
        } else {
            handException(exception, false);
            return null;
        }
    }


    /**
     * 空指针异常
     *
     * @param exception
     * @param request
     * @return
     */
    @ExceptionHandler({NullPointerException.class})
    @ResponseBody
    public Result<String> exceptionNull(Exception exception) throws Exception {
//        Response<String> result = result500("空指针异常1:" + exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;
        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result500("null null null!!!!!!");
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_500, "null null null!!!!!!");
            return result;
            // 返回错误页面
        } else {
            handException(exception, false);
            return null;
        }
    }

    @ExceptionHandler({MethodArgumentTypeMismatchException.class})
    @ResponseBody
    public Result<String> exceptionMethodArgumentTypeMismatchException(Exception exception) throws Exception {
        sendExceptionInfo(exception);
        if (ServletUtil.isAjaxRequest()) {
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_400, "param mismatch");
            return result;
        } else {
            handException(exception, false);
            return null;
        }
    }

    @ExceptionHandler({RestClientException.class})
    @ResponseBody
    public Result<String> exceptionRestClientException(Exception exception) throws Exception {
        sendExceptionInfo(exception);
        if (ServletUtil.isAjaxRequest()) {
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_500, "server remote error");
            return result;
        } else {
            handException(exception, false);
            return null;
        }
    }

    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    @ResponseBody
    public Result<String> exceptionHttpRequestMethodNotSupportedException(Exception exception) throws Exception {
        sendExceptionInfo(exception);
        if (ServletUtil.isAjaxRequest()) {
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_400, "http method not supported");
            return result;
        } else {
            handException(exception, false);
            return null;
        }
    }

    @ExceptionHandler(value = {
            DataAccessException.class,
            SQLException.class
    })
    @ResponseBody
    public Result<String> exceptionDbException(Exception exception) throws Exception {
        sendExceptionInfo(exception);
        if (ServletUtil.isAjaxRequest()) {
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_500, "server data error");
            return result;
        } else {
            handException(exception, false);
            return null;
        }
    }

    /**
     * @param exception
     * @param request
     * @return
     * @ExceptionHandler在此处定义全局处理，通过@ExceptionHandler的value属性可过滤拦截的条件， 再此我们可以看出我们拦截所有的Exception
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    protected Result<String> exception(Exception exception) throws Exception {
//        Response<String> result = result500(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;

//        exception.printStackTrace();
        sendExceptionInfo(exception);
        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
//            Result<String> result = result500("Server Error");
            Result<String> result = ResultBuilder.buildResult(ErrorCode.code_500, "Server Error");
            return result;
            // 返回错误页面
        } else {
            handException(exception, false);
            return null;
        }
    }

    /**
     * @param exception
     * @param request
     * @return
     * @ExceptionHandler在此处定义全局处理，通过@ExceptionHandler的value属性可过滤拦截的条件， 再此我们可以看出我们拦截所有的Exception
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseBody
    protected Result<String> exceptionIllegalArgumentException(Exception exception) throws Exception {
//        Response<String> result = result500(exception.getMessage());
//        sendExceptionInfo(exception);
//        return result;

//        exception.printStackTrace();
        sendExceptionInfo(exception);
        StackTraceElement[] stackTrace = exception.getStackTrace();
        String className = stackTrace[1].getClassName();
        String errorMessage = exception.getMessage();
        //如果不是自定义的断言抛的，则不往外提示
        boolean isAssert = true;
        if (!className.endsWith("HlAssert")) {
            errorMessage = "Server Error";
            isAssert = false;
        }

        // 如果是ajax请求,返回json
        if (ServletUtil.isAjaxRequest()) {
            Result<String> result = null;
            //正常的业务提示，走400
            if (isAssert) {
//                result = result400(errorMessage);
//                result = result600(errorMessage);
                result = ResultBuilder.buildResult(ErrorCode.code_600, errorMessage);
                //服务端内部异常 走500
            } else {
//                result = result500(errorMessage);
                result = ResultBuilder.buildResult(ErrorCode.code_500, errorMessage);
            }
            return result;
            // 返回错误页面
        } else {
            handException(exception, isAssert);
            return null;
        }
    }

    private void handException(Exception exception, boolean isAssert) throws Exception {
        //如果是由feign调用的，则直接抛到上游
        HttpServletRequest request = RequestContext.getRequest();
        String feignHeader = request.getHeader(FeignBasicAuthRequestInterceptor.headerKey);
        if (StringUtils.isNotEmpty(feignHeader)) {
            throw exception;
        }

        Model model = RequestContext.getModel();
        if (null != model) {
            //系统内部异常，异常信息，不展现到前端
            if (!isAssert) {
                model.addAttribute("errorMessage", "server error，please contact your system admin or technical support group for further assistance,email: zwllxs@qq.com/18721279166，thank you");
                // 以下为兼容内置的错误视图，否则会报如EL1008E: Property or field 'timestamp' cannot be found on
                // object of type 'java.util.HashMap' 这样的异常
                model.addAttribute("timestamp", DateUtil.formatDate(new Date()));
                model.addAttribute("error", HttpStatus.INTERNAL_SERVER_ERROR.name());
                model.addAttribute("status", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
            } else {
                //业务异常提示，异常信息，原样展现到前端
                model.addAttribute(CoreConstant.operationToastError, exception.getMessage());
            }
        }
        log.error("系统异常," + exception.getMessage(), exception);
//        request().getRequestDispatcher("500").forward(request(), response());
//        return "500";
        //异步任务没有上下文
        if (null != model) {
            String content = null;
            try {
                content = TemplatesUtil.createTemplates(model.asMap(), "500", templateEngine);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("异常处理失败", e);
                Map<String, Object> map = model.asMap();
                //            map.remove("pageInfo");
                if (null != map) {
                    map.put("title", "server error detail (if possiable,it's better to config a 500 page)");
                    content = JSON.toJSONString(map);
                }
            }
            PrintWriter writer = RequestContext.getResponse().getWriter();
            writer.write(content);
            writer.flush();
            writer.close();
        }
    }

    /**
     * 发送异常处理
     *
     * @param exception
     * @param request
     * @return
     */
    private boolean sendExceptionInfo(Exception exception) {
        String errorMessage = exception.getMessage();
        //此异常没找到好办法，无须监控
        if (StringUtils.isNotEmpty(errorMessage)) {
            if (errorMessage.contains("java.io.IOException: Broken pipe")) {
                return true;
            }
        }
        Map<String, String>[] requestInfoMap = ServletUtil.getClientInfoStat(RequestContext.getRequest());
        sendExceptionInfo(requestInfoMap, exception);
        return false;
    }

    /**
     * 单应用系统中在本controller中直接调用，如果是分布式系统且引用本core，则从网关调过来的controller，
     * 是没有Service的，
     * 故不能直接调用本方法，介时的controller，继承此方法即可处理全部异常
     *
     * @param requestInfoMap
     * @param ex
     */
    public void sendExceptionInfo(Map<String, String>[] requestInfoMap, Exception ex) {
        Boolean isAssert = false;
        if (ex instanceof IllegalArgumentException) {
            StackTraceElement[] stackTrace = ex.getStackTrace();
            String className = stackTrace[1].getClassName();
            isAssert = className.endsWith("HlAssert");
//            if (className.endsWith("Assert")) {
//                isAssert = true;
//            }
        }

        //在aop的环绕后面写日志时，如果用异常机制弹出了提示或抛出了异常，会导入日志执行不到，即环绕后面执行不到，此情况会执行到此处，则此处可以把日志入库
        HttpServletRequest request = RequestContext.getRequest();
        Object proceedingJoinPoint = request.getAttribute(AbstractAop.methodJoinPointKey);
        AbstractLog abstractLog = (AbstractLog) request.getAttribute(CoreConstant.userLog);
        if (null != abstractLog) {
            abstractLog.setExceptionMessage(ex.getMessage());
            request.setAttribute(CoreConstant.userLog, abstractLog);
            try {
                aopService.handLog(RequestContext.getHandler(), request, (ProceedingJoinPoint) proceedingJoinPoint);
            } catch (Exception e) {
//                e.printStackTrace();
                log.error("日志入库失败", e);
            }
        }

        //如果是断言，或是错误代码，是业务逻辑判断，就不用打印异常堆栈了
//        if (!isAssert||!(ex instanceof ErrorCodeException)) {
        if (!isAssert) {
            ex.printStackTrace();
            if (null != sysExceptionService) {
                sysExceptionService.sendExceptionInfo(requestInfoMap, proceedingJoinPoint, RequestContext.getHandler(), ex);
            }
        }
    }

}